package com.orcwar.engine.pathfinding;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import android.util.Log;

import com.orcwar.engine.OWMap;
import com.orcwar.engine.OWTile;
import com.orcwar.engine.OWUnit;
import com.orcwar.engine.tile.OWAttackTile;
import com.orcwar.enums.OWAttackTileTypeEnum;

/**
 * 
 * @author fbastien
 */
public final class TileScanner {

	private static final int MOVE_THRESHOLD_ASTAR = 8;

	/**
	 * Util class should not be instanciated.
	 */
	private TileScanner() {}
	
	/**
	 * Computes the list of tiles that a unit can move to.
	 * 
	 * @param map
	 * @param unit
	 *            the unit.
	 * @param pathfinder
	 */
	public static Set<OWTile> findPossibleTilesToMove(OWMap map,
			OWUnit unit, AStarPathFinder pathfinder) {
		Set<OWTile> possibleTilesToMove = new HashSet<OWTile>();

		OWTile unitTile = unit.getTile();
		possibleTilesToMove.add(unitTile);

		// TEST : Use A* all the time
		if (unit.getMovePoints() > MOVE_THRESHOLD_ASTAR) {
			// A*

			List<OWTile> list;
			for (List<OWTile> row : map.getTiles()) {
				for (OWTile tile : row) {
					list = pathfinder.findPath(unit, unitTile.getX(),
							unitTile.getY(), tile.getX(), tile.getY());
					if (list != null) {
						// The unit can go to this tile
						possibleTilesToMove.add(tile);
					}
				}
			}
		} else {
			// BFS (start from unit tile)
			// left
			findPossibleTilesToMove(map, possibleTilesToMove, unit,
					unit.getMovePoints(), unitTile.getX() - 1, unitTile.getY());
			// down
			findPossibleTilesToMove(map, possibleTilesToMove, unit,
					unit.getMovePoints(), unitTile.getX(), unitTile.getY() + 1);
			// right
			findPossibleTilesToMove(map, possibleTilesToMove, unit,
					unit.getMovePoints(), unitTile.getX() + 1, unitTile.getY());
			// up
			findPossibleTilesToMove(map, possibleTilesToMove, unit,
					unit.getMovePoints(), unitTile.getX(), unitTile.getY() - 1);
		}

		return possibleTilesToMove;
	}

	/**
	 * Recursive method to get possible tiles to move to.
	 * 
	 * @param map
	 */
	private static void findPossibleTilesToMove(OWMap map,
			Set<OWTile> tilesPossible, OWUnit unit, int remainingMovePoints,
			int x, int y) {
		if (remainingMovePoints > 0) {
			// still got movement, get next tile
			OWTile next = map.getTile(x, y);
			if (next != null && next.canGoThrough(unit)) {
				// the tile exists and we can move to it, add it
				tilesPossible.add(next);
				// and continue
				// left
				findPossibleTilesToMove(map, tilesPossible, unit,
						remainingMovePoints - 1, next.getX() - 1, next.getY());
				// down
				findPossibleTilesToMove(map, tilesPossible, unit,
						remainingMovePoints - 1, next.getX(), next.getY() + 1);
				// right
				findPossibleTilesToMove(map, tilesPossible, unit,
						remainingMovePoints - 1, next.getX() + 1, next.getY());
				// up
				findPossibleTilesToMove(map, tilesPossible, unit,
						remainingMovePoints - 1, next.getX(), next.getY() - 1);
			}
		}
		;
	}

	/**
	 * Computes the list of tiles that a unit can attack to after moving to any
	 * tile of a given set.
	 * 
	 * @param map
	 * @param unit
	 *            the unit.
	 * @param possibleTilesToMove
	 *            the list of tiles the unit can attack from.
	 */
	public static Set<OWAttackTile> findPossibleTilesToAttack(OWMap map, OWUnit unit,
			Set<OWTile> possibleTilesToMove) {
		Set<OWAttackTile> possibleTilesToAttack = new HashSet<OWAttackTile>();

//		for (OWTile tileFrom : possibleTilesToMove) {
//			for (List<OWTile> row : map.getTiles()) {
//				for (OWTile tile : row) {
//					if (tile.allowsAttacks()
//							&& unit.isRangeOk(tileFrom.getX(), tileFrom.getY(),
//									tile.getX(), tile.getY())) {
//						possibleTilesToAttack.add(tile);
//					}
//				}
//			}
//		}
		

		for (OWTile unitTile : possibleTilesToMove) {
			
			//1 - Check the mandatory available tiles
			OWTile[] mandatoryTiles = unit.getGlobalMandatoryAvailableTiles(map, unitTile);
			if (mandatoryTiles != null) {
				for (OWAttackTile tile : unit.getGlobalMandatoryAvailableTiles(map, unitTile)) {
					if (!unit.isMandatoryTileAvailable(tile)) {
						Log.i(TileScanner.class.getName(), "Unit cannot attack because the mandatory tiles are not all available !");
						return possibleTilesToAttack;
					}
				}
			}
			
			//2 - Check each attackable tile			
			for (OWAttackTile[] subList : unit.getAttackTiles(map, unitTile)) {
				for (OWAttackTile tile : subList) {
					if (tile == null) continue;
					
					if (tile.getType().contains(OWAttackTileTypeEnum.ATTACKABLE)) {
						if (unit.canAttackTile(tile)) {
							possibleTilesToAttack.add(tile);
						} else {
							break;
						}
					}
					if (tile.getType().contains(OWAttackTileTypeEnum.MANDATORY_NOT_WALL)
							&& !unit.isMandatoryTileAvailable(tile)) {
						break;
					}
					if (tile.getType().contains(OWAttackTileTypeEnum.MANDATORY_NOT_UNIT)
							&& !unit.isMandatoryTileAvailable(tile)) {
						break;
					}
				}
			}
		}

		return possibleTilesToAttack;
	}

	/**
	 * Computes the list of tiles that a unit can attack to from where it is.
	 * 
	 * @param map the map
	 * @param unit the unit
	 * @return the list of tile ID where the unit can attack
	 */
	public static Set<OWAttackTile> findPossibleTilesToAttack(OWMap map, OWUnit unit) {
		return findPossibleTilesToAttack(map, unit,
				Collections.singleton(unit.getTile()));
	}
}
